Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

26장. API 개발 — 기존 Convention을 따르게 만들기

6부는 매일 하는 작업들이다.

첫 번째는 API 추가.

Agent가 가장 빠르게 해내는 작업이면서,
가장 티 나게 틀리는 작업이기도 하다.


문제는 만드는 것이 아니다

“주문 취소 API를 만들어줘” 라고 하면
Agent는 30초 만에 완성도 높은 코드를 낸다.

Controller, Service, DTO, 예외 처리까지 갖춰져 있다.

문제는 그 코드가 우리 프로젝트의 것처럼 보이지 않는다는 점이다.

우리 프로젝트          Agent가 만든 것
────────────────      ────────────────
ApiResponse<T> 래핑    바로 DTO 반환
BusinessException      IllegalArgumentException
Facade에서 조합        Controller에서 Service 두 개 호출
@field:NotNull         @NotNull

각각은 틀린 코드가 아니다.
다만 우리 것이 아니다.

API 개발에서 Agent에게 시킬 일은
새로 만드는 것이 아니라 닮게 만드는 것이다.


유사 API를 지목한다

가장 효과가 큰 지시 방법이다.

주문 취소 API를 추가해줘.

@src/main/kotlin/order/OrderConfirmController.kt 와
@src/main/kotlin/order/OrderConfirmFacade.kt 를 먼저 읽고
같은 구조로 만들어줘.

다른 점은 이것뿐이야:
- 취소는 이미 배송이 시작된 주문이면 400을 반환한다

이 지시가 하는 일은 셋이다.

  • 컨벤션을 문서가 아니라 코드로 전달한다
  • Agent가 탐색할 필요가 없다 (13장)
  • 차이점만 판단하면 되니 실수가 줄어든다

⚠️ 이때 지목하는 파일은
가장 최근에 잘 만든 것이어야 한다.

레거시에는 세대가 섞여 있다.
오래된 것을 지목하면 오래된 패턴이 복제된다.


계층별 실패 패턴

Kotlin + Spring 프로젝트에서 반복되는 것들이다.

계층자주 나는 사고
Controller응답 래퍼 누락, 상태 코드 임의 선택
Facade만들지 않고 Controller에서 Service 여러 개 호출
Service다른 도메인 Service 직접 호출
Repository필요 없는 조회 추가, 페이징 누락
DTO엔티티를 그대로 반환
Validation검증 위치가 계층마다 다름
Exception새 예외 클래스 생성

🔥 이 중 두 개는 매번 나온다.

엔티티 직접 반환새 예외 생성이다.

둘 다 Agent 입장에서는 자연스러운 선택이다.
엔티티에 필요한 필드가 다 있고, 새 예외가 의미를 정확히 표현한다.

우리 사정을 모르면 그렇게 한다.


반복되는 것은 규칙으로

같은 지적을 두 번 했다면 CLAUDE.md 로 올린다.
15장의 기준이다.

## API Convention

- 응답은 `ApiResponse<T>` 로 감싼다. 엔티티를 직접 반환하지 않는다
- 예외는 `common/exception` 의 기존 것을 쓴다. 새로 만들지 않는다
- 도메인 간 조합은 Facade에서 한다. Controller는 Facade 하나만 호출한다
- 요청 검증은 DTO의 `@field:` 애노테이션으로 한다
- 상태 코드: 검증 실패 400, 권한 403, 없음 404, 도메인 규칙 위반 409

마지막 줄이 특히 값지다.

상태 코드 선택은 정답이 없어서
Agent가 매번 다르게 고른다.

우리 팀의 답을 한 줄로 적어두면 끝난다.


계약을 먼저 정한다

구현 전에 스펙을 확정하면 되돌릴 일이 줄어든다.

구현하기 전에 API 스펙만 먼저 제시해줘.

- 경로, 메서드, 요청/응답 JSON
- 실패 케이스별 상태 코드와 에러 코드
- 기존 API와 다른 점

동의하면 그때 구현하자.

21장의 Plan First를 API 작업에 맞춘 형태다.

스펙은 되돌리기 비용이 큰 결정이다.
클라이언트가 이미 붙었으면 바꿀 수 없다.


완료 조건

10장의 형식으로 쓰면 이 정도다.

## Acceptance Criteria
- 정상 취소 요청 200 + ApiResponse 래핑 확인 테스트
- 배송 시작된 주문 취소 시 409 + 에러 코드 `ORDER_ALREADY_SHIPPED`
- 존재하지 않는 주문 404
- 권한 없는 사용자 403
- `./gradlew test --tests '*OrderCancelApi*'` 통과
- `./gradlew ktlintCheck` 통과

⚠️ 실패 케이스를 완료 조건에 넣지 않으면
Agent는 happy path만 만들고 끝낸다.

30장에서 이 성향을 다시 다룬다.


API 문서와 클라이언트 영향

잊기 쉬운 두 가지다.

이 API 추가가 영향을 주는 곳을 확인해줘.

- OpenAPI 스펙 파일 갱신이 필요한가
- 기존 응답 스펙이 바뀌는가 (클라이언트 배포 필요 여부)
- 이 엔드포인트를 호출하는 내부 서비스가 있는가

세 번째가 모놀리스에서 특히 중요하다.

같은 레포 안에서 다른 모듈이
이 Service를 직접 호출하고 있을 수 있다.

38장에서 이 호출 관계가 경계 탐색의 재료가 된다.


이 장의 핵심

  • Agent는 API를 잘 만든다 — 다만 우리 프로젝트의 것처럼 보이지 않는다
  • 시킬 일은 새로 만드는 것이 아니라 기존 것과 닮게 만드는 것이다
  • 유사 API를 지목하면 컨벤션이 문서가 아니라 코드로 전달된다
  • 지목할 파일은 가장 최근에 잘 만든 것이어야 한다
  • 엔티티 직접 반환과 새 예외 생성은 매번 나오는 두 가지다
  • 상태 코드 선택 기준을 한 줄로 적어두면 매번 흔들리지 않는다
  • 스펙은 되돌리기 비용이 크다 — 구현 전에 확정한다
  • 실패 케이스를 완료 조건에 넣지 않으면 happy path만 만들어진다
  • 모놀리스에서는 내부 호출자 확인이 빠지기 쉽다